package action

import (
	"fmt"
	"strings"
	"time"

	"gitee.com/sansaniot/ssiot-core/facade/env"
	"gitee.com/sansaniot/ssiot-core/logger"
	"github.com/tidwall/sjson"
	"gorm.io/gorm"

	"ssdevice/common/constant"
	"ssdevice/common/enums"
	"ssdevice/common/middleware/mqtt"
	"ssdevice/common/utils"
	"ssdevice/internal/data/format"
	"ssdevice/internal/device/models"
	"ssdevice/internal/device/models/command"
	"ssdevice/internal/device/service"
)

type Login struct {
	Orm *gorm.DB
}

func (action *Login) Execute(topic string, jsonData map[string]interface{}) {
	devSn := strings.Split(topic, "/")[3]
	devType, to := format.GetFromTo(devSn, jsonData, true)
	if len(devType) == 0 {
		// 异常
		ackTopic := fmt.Sprintf("/iot/%s/%s/%s/ack", devSn, constant.TopicPlaceholder, enums.Login.Get().Name)
		ackData := map[string]interface{}{
			"sid":  jsonData["sid"],
			"from": constant.TopicPlaceholder,
			"to":   to,
			"code": 500,
			"msg":  "platform db error",
			"data": map[string]interface{}{
				"ak":   "",
				"goto": "",
			},
		}
		mqtt.MqttClient.Publish(ackTopic, utils.MapToBytes(ackData))
		return
	}
	jsonData[constant.CacheLoginInfo] = map[string]interface{}{
		constant.CacheDevSn:   devSn,
		constant.CacheDevType: devType,
		constant.CacheTo:      to,
		constant.CacheSource:  devSn,
	}
	if _, err := env.Cache.LPush(constant.DeviceLoginCacheQueue, string(utils.MapToBytes(jsonData))); err != nil {
		logger.Errorf("登录消息队列lpush err:%s", err.Error())
	}
}

// 1、登录消息顺序消费
// 2、登录消息来源：设备登录包、平台自判断
func (action *Login) Run(threadName string) {
	go func() {
		for {
			select {
			case <-time.After(200 * time.Millisecond):
				result, _ := env.Cache.RPop(constant.DeviceLoginCacheQueue)
				if len(result) > 0 {
					action.Login(utils.BytesToMap([]byte(result)))
				}
			}
		}
	}()
}

// 登录业务处理
func (action *Login) Login(jsonData map[string]interface{}) {
	cacheInfo := jsonData[constant.CacheLoginInfo].(map[string]interface{})
	devSn := cacheInfo[constant.CacheDevSn].(string)
	devType := cacheInfo[constant.CacheDevType].(string)
	to := cacheInfo[constant.CacheTo].(string)
	source := cacheInfo[constant.CacheSource].(string)
	isAutoLogin := cacheInfo[constant.CacheAutoLogin] != nil
	// 初始化判断
	theDev := action.initDevice(devSn, devType, to, jsonData, source, isAutoLogin)
	// 更新状态
	dev := &models.SysDev{}
	dev.Model.Orm = action.Orm
	if err := dev.UpdateOnlineStatus(devSn, to, utils.GetNowTimeStr(), enums.Online); err != nil {
		logger.Error(err)
	}
	if !isAutoLogin {
		// goto获取
		goTo := ""
		if len(theDev.Id) > 0 {
			goTo = format.GetLoginRedirectAddr(action.Orm, theDev)
		}
		// 响应
		logInOutAck(devSn, to, enums.Login.Get().Name, jsonData, goTo)
		// 广播
		pubLoginout(devSn, to, enums.LoginMsg, jsonData, theDev.DeptId)
	}
}

// 初始化(不存在的设备)
func (action *Login) initDevice(devSn, devType, to string, jsonData map[string]interface{}, source string, autoLogin bool) (theDev models.SysDev) {
	var err error
	productModel := utils.InterfaceToStr(utils.GetFieldFromJson([]string{"data", "model"}, jsonData))
	// 平台自登录已判断设备不存在
	if !autoLogin {
		if len(to) > 0 && devSn != to {
			// to为设备sn，初始化
			action.initDevice(to, enums.DevTypeOfDevice, "", jsonData, devSn, false)
			return
		}
		dev := &models.SysDev{}
		dev.Model.Orm = action.Orm
		theDev, err = dev.FindDeviceBySn(devSn)
		// 存在
		if err != nil || len(theDev.Id) > 0 {
			if len(productModel) > 0 && theDev.ProductModel == constant.DefaultProductCode && productModel != constant.DefaultProductCode {
				// 先默认，后又上报了，则重新定义设备
				_ = dev.RemoveDevBySn(devSn)
			} else {
				return
			}
		}
	}
	// 不存在
	devService := &service.Device{}
	devService.Orm = action.Orm
	devService.SetAllModel()
	addReq := command.DeviceInsertReq{
		Sn:            devSn,
		Name:          devSn,
		ProductModel:  "",
		Location:      nil,
		SoftCmd:       "",
		FromLoginInit: true,
	}
	addReq.Type = devType
	// 按产品初始
	if len(productModel) > 0 {
		addReq.ProductModel = productModel
	} else {
		// 默认产品
		addReq.ProductModel = constant.DefaultProductCode
		addReq.Type = enums.DevTypeOfDevice
	}
	if addReq.Type == enums.DevTypeOfDevice && source != devSn {
		addReq.GatewaySn = source
	}
	logger.Infof("初始化接入设备[%s]-类型[%s]", devSn, addReq.Type)
	if err, theDev = devService.AddDevice(&addReq); err != nil {
		logger.Error(err)
	}
	return
}

// 平台自动登录，数据包里没有from/to、设备类型判断不准确，页面手动修改
func AutoLogin(db *gorm.DB, devSn string, jsonData map[string]interface{}, gateSn ...string) (devType, to string) {
	prepare(jsonData)
	devType, to = format.GetFromTo(devSn, jsonData, false)
	if len(to) > 0 && to != devSn {
		// to是设备
		m := map[string]interface{}{}
		if pm, ok := jsonData[constant.CacheModel]; ok {
			m[constant.CacheModel] = pm
		}
		devType = enums.DevTypeOfGateway
		// 初始 先gate 后dev
		defer AutoLogin(db, to, m, devSn)
	}
	devCache, err := env.Cache.HashGet(constant.DeviceEasyCache, devSn)
	if err != nil && !strings.Contains(err.Error(), "redis: nil") {
		// redis异常不允许往下执行 避免重复创建已有设备
		logger.Error(err)
		return
	}
	if len(devCache) > 0 {
		// 设备存在
		info := utils.BytesToMap([]byte(devCache))
		theModel := utils.If(info["productModel"] != nil, info["productModel"], "").(string)
		productModel := utils.InterfaceToStr(jsonData[constant.CacheModel])
		if len(productModel) > 0 && theModel == constant.DefaultProductCode && productModel != constant.DefaultProductCode {
			// 先默认，后又上报了，则重新定义设备
			dev := &models.SysDev{}
			dev.Model.Orm = db
			_ = dev.RemoveDevBySn(devSn)
		} else {
			return
		}
	}
	// 不存在，自动登录
	m := map[string]interface{}{
		constant.CacheDevSn:     devSn,
		constant.CacheSource:    devSn,
		constant.CacheTo:        to,
		constant.CacheAutoLogin: true,
	}
	// 默认device类型
	devType = utils.If(len(devType) == 0, enums.DevTypeOfDevice, devType).(string)
	if len(gateSn) > 0 && gateSn[0] != devSn {
		m[constant.CacheSource] = gateSn[0]
	}
	m[constant.CacheDevType] = devType
	// 登录消费队列
	loginData := map[string]interface{}{
		"sid":                   -1,
		constant.CacheLoginInfo: m,
	}
	// 应用兼容，sensor上报含model按产品自动创建设备
	if devType == enums.DevTypeOfDevice && jsonData[constant.CacheModel] != nil {
		datastr := string(utils.MapToBytes(loginData))
		datastr, _ = sjson.Set(datastr, "data.model", jsonData[constant.CacheModel])
		loginData = utils.BytesToMap([]byte(datastr))
	}
	loginAction := &Login{Orm: db}
	loginAction.Login(loginData)
	return
}

func prepare(jsonData map[string]interface{}) {
	pdmodel := utils.GetFieldFromJson([]string{"data", constant.CacheModel}, jsonData)
	if pdmodel != nil {
		delete(jsonData["data"].(map[string]interface{}), constant.CacheModel)
		jsonData[constant.CacheModel] = pdmodel
	}
}
